package com.ideaaedi.commonspring.aop;

import com.ideaaedi.commonspring.entity.dto.RateLimitInfoDTO;
import com.ideaaedi.commonspring.event.RateLimitValidateEvent;
import com.ideaaedi.commonspring.lite.ratelimiter.RateLimitException;
import com.ideaaedi.commonspring.lite.ratelimiter.RateLimiter;
import com.ideaaedi.commonspring.lite.ratelimiter.RateLimiterChain;
import com.ideaaedi.commonspring.parser.SpelUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 限流切面
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2100.10.17.LTS17
 */
@Slf4j
@Aspect
public class RateLimiterAdvice implements Ordered {

    public static final String BEAN_NAME = "rateLimiterAdvice";

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private ApplicationContext applicationContext;

    private final int order;

    public RateLimiterAdvice(int order) {
        this.order = order;
    }

    @Around("@annotation(com.ideaaedi.commonspring.lite.ratelimiter.RateLimiter)"
            + " || "
            + "@annotation(com.ideaaedi.commonspring.lite.ratelimiter.RateLimiterChain)")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        Object[] args = proceedingJoinPoint.getArgs();

        List<RateLimiter> annoList = new ArrayList<>();
        RateLimiter redisCacheEvict = method.getAnnotation(RateLimiter.class);
        if (redisCacheEvict != null) {
            annoList.add(redisCacheEvict);
        }
        RateLimiterChain rateLimiterChain = method.getAnnotation(RateLimiterChain.class);
        if (rateLimiterChain != null) {
            RateLimiter[] rateLimiterArr = rateLimiterChain.value();
            if (rateLimiterArr != null && rateLimiterArr.length > 0) {
                Collections.addAll(annoList, rateLimiterArr);
            }
        }
        for (RateLimiter rateLimiter : annoList) {
            doRateLimit(method, args, rateLimiter);
        }
        return proceedingJoinPoint.proceed();
    }
    
    @Override
    public int getOrder() {
        return this.order;
    }

    /**
     * 限流校验
     *
     * @param method 目标方法
     * @param args 目标方法的参数值
     * @param rateLimiterAnno 限流注解
     */
    private void doRateLimit(Method method, Object[] args, RateLimiter rateLimiterAnno) {
        String uid = rateLimiterAnno.uid();
        long tokenLimit = rateLimiterAnno.tokenLimit();
        long interval = rateLimiterAnno.interval();
        TimeUnit intervalUnit = rateLimiterAnno.intervalUnit();
        String condition = rateLimiterAnno.condition();
        if (tokenLimit <= 0L) {
            return;
        }
        if (interval <= 0) {
            return;
        }
        long intervalMillisSecond = intervalUnit.toMillis(interval);
        if (intervalMillisSecond <= 0) {
            return;
        }
        Boolean doLimit = true;
        if (StringUtils.isNotBlank(condition)) {
            if (condition.contains("#")) {
                doLimit = doSpelParse(method, args, Boolean.class, condition);
                log.debug("doLimit(after spel parse) -> {}", doLimit);
            } else {
                doLimit = "true".equalsIgnoreCase(condition);
            }
        }
        if (BooleanUtils.isNotTrue(doLimit)) {
            return;
        }
        uid = doSpelParse(method, args, String.class, uid);
        log.debug("uid(after spel parse) -> {}", uid);
        // 获取限流器
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(uid);
        // 限流规则配置
        if (!rateLimiter.isExists()) {
            rateLimiter.trySetRate(RateType.OVERALL, tokenLimit, intervalMillisSecond, RateIntervalUnit.MILLISECONDS);
        }
        // 获取令牌
        boolean obtainedToken = rateLimiter.tryAcquire();
        // 发布一个事件，以便业务侧进行相关处理（如：对相关redis-key设置过期时间）
        applicationContext.publishEvent(
                new RateLimitValidateEvent(
                    new RateLimitInfoDTO(method, rateLimiterAnno, uid, obtainedToken)
                )
        );
        if (!obtainedToken) {
            throw new RateLimitException(uid, rateLimiterAnno);
        }
    }
    
    /**
     * 解析spel
     */
    @SuppressWarnings("unchecked")
    private static <T> T doSpelParse(Method method, Object[] args, Class<T> clazz, String spel) {
        if (spel == null || !spel.contains("#")) {
            return (T)spel;
        }
        return (T)SpelUtil.parseSpel(method, args, clazz, spel);
    }
}